package cn.maihe.elg.operation.centers.epoint.channel.service;

import cn.maihe.elg.operation.centers.epoint.channel.EpointBaseChannelService;
import cn.maihe.elg.operation.centers.epoint.dto.EpointBaseRespDTO;
import cn.maihe.elg.operation.centers.epoint.dto.channel.EpointDownloadPublickeyReqDTO;
import cn.maihe.elg.operation.centers.epoint.dto.channel.MeiShanEpointBaohanFileCompleteNoticeReqDTO;
import cn.maihe.elg.operation.centers.epoint.dto.channel.MeiShanEpointUploadBaohanFileReqDTO;
import cn.maihe.elg.operation.centers.epoint.enums.EpointChannelMethod;
import cn.maihe.elg.operation.centers.epoint.enums.EpointRespCodeEnum;
import cn.maihe.elg.operation.centers.hymake.auth.utils.BCECUtil;
import cn.maihe.elg.operation.centers.hymake.auth.utils.SM2Util;
import cn.maihe.elg.operation.centers.supports.model.bo.InnerUploadBaohanFileBo;
import cn.maihe.elg.operation.model.dto.InnerBaseResp;
import cn.maihe.elg.operation.model.enums.AttachmentFileType;
import cn.maihe.elg.operation.model.enums.CenterNoEnum;
import cn.maihe.elg.operation.repository.entity.AcceptOrderInfoDO;
import cn.maihe.elg.operation.repository.entity.AcceptOrderInfoDOCondition;
import cn.maihe.elg.operation.repository.entity.ProjectInfoDO;
import cn.maihe.elg.operation.service.business.ProjectInfoService;
import cn.maihe.elg.operation.service.order.AcceptOrderInfoService;
import cn.maihe.elg.operation.service.order.GuaranteeAttachmentInfoService;
import cn.maihe.elg.operation.supports.system.ElgResourceService;
import cn.maihe.elg.operation.utils.SM4Util;
import cn.maihe.elg.operation.utils.ValidateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Description 金融服务系统作为服务方，金融机构作为请求方。
 * 为了规范电子保函业务，金融机构的保函业务需要满足以下几个要求：
 * 1. 金融机构开具的保函必须为电子件，不接受纸质文件或纸质文件的电子扫描件；
 * 2. 金融机构使用第三方CA机构颁发的数字证书进行电子签名（签章），电子保函相关文件必须遵循PKI体系的数字签名系统和数字证书技术标准和规范，具有明确的法律效力；
 * SM4对整个文件进行加密，加密后进行安全传输
 * @Author SSX
 * @Date 2025/05/28
 */
@Service
@Slf4j
public class MeiShanUploadBaohanFileChannelService extends EpointBaseChannelService<MeiShanEpointUploadBaohanFileReqDTO, EpointBaseRespDTO> {

    @Resource
    private ElgResourceService elgResourceService;
    @Resource
    private GuaranteeAttachmentInfoService guaranteeAttachmentInfoService;
    @Resource
    private NPEpointDownloadPublickeyChannelService downloadPublickeyChannelService;
    @Resource
    private MeiShanEpointBaohanFileCompleteNoticeChannelService meiShanEpointBaohanFileCompleteNoticeChannelService;
    @Resource
    private AcceptOrderInfoService acceptOrderInfoService;
    @Resource
    private ProjectInfoService projectInfoService;

    @Override
    public EpointChannelMethod methodEnum() {
        return EpointChannelMethod.uploadbaohanfile;
    }

    /**
     * 128位
     */
    public byte[] generateKey() throws Exception {
        return SM4Util.generateKey();
    }

    /**
     * 128位
     */
    public byte[] generateIV() throws Exception {
        return SM4Util.generateKey();
    }

    public String getSm4Key(CenterNoEnum centerNoEnum) {
        return this.epointCenterInfosConfig.getAuthConfig(centerNoEnum).getCenterInfo().getSm4Key();
    }

    public String getSm4Iv(CenterNoEnum centerNoEnum) {
        return this.epointCenterInfosConfig.getAuthConfig(centerNoEnum).getCenterInfo().getSm4Iv();
    }

    /**
     * base64编码后字符串
     */
    protected String downloadPublicKey(String acceptOrderNo, CenterNoEnum centerNoEnum, String applyNo) {
        EpointDownloadPublickeyReqDTO reqDTO = new EpointDownloadPublickeyReqDTO();
        reqDTO.setApplyno(applyNo);
        InnerBaseResp<String> innerBaseResp = downloadPublickeyChannelService.downloadPublickey(acceptOrderNo, centerNoEnum, reqDTO);
        if (innerBaseResp.getRespCode().isSuccess()) {
            return innerBaseResp.getData();
        }
        throw new RuntimeException(innerBaseResp.getMessage());
    }

    public InnerBaseResp uploadBaohanFile(String acceptOrderNo, CenterNoEnum centerNoEnum, InnerUploadBaohanFileBo bo) {
        InnerBaseResp<String> innerBaseResp = null;

        try {
            ValidateUtil.validate(bo);
        } catch (Exception e) {
            innerBaseResp = InnerBaseResp.error("保函文件上传参数有误:" + e.getMessage());
            return innerBaseResp;
        }

        String sm2PublicKey = null;
        try {
            sm2PublicKey = this.downloadPublicKey(acceptOrderNo, centerNoEnum, bo.getApplyno());
            log.info("{} << 下载sm2公钥值: {}", acceptOrderNo, sm2PublicKey);
        } catch (Exception e) {
            innerBaseResp = InnerBaseResp.error(e.getMessage());
            return innerBaseResp;
        }
        Assert.hasText(sm2PublicKey, () -> "下载sm2公钥内容为空");

        try {
            AttachmentFileType fileType;
            fileType = AttachmentFileType.TYPE_0;

            Path guaranteePath = guaranteeAttachmentInfoService.getGuaAttPath(bo.getAcceptOrderNo(), fileType);
            if (guaranteePath == null || Files.notExists(guaranteePath)) {
                throw new RuntimeException("保函文件不存在");
            }

            byte[] keyByte = SM4Util.generateKey();
            byte[] ivByte = SM4Util.generateKey();
            log.debug("MeiShanUploadBaohanFileChannelService {} << keyByte: {}", acceptOrderNo, Arrays.toString(keyByte));
            log.debug("MeiShanUploadBaohanFileChannelService {} << ivByte: {}", acceptOrderNo, Arrays.toString(ivByte));
            log.debug("MeiShanUploadBaohanFileChannelService {} <<encodeBase64String keyByte: {}", acceptOrderNo, Base64.encodeBase64String(keyByte));
            log.debug("MeiShanUploadBaohanFileChannelService {} <<encodeBase64String ivByte: {}", acceptOrderNo, Base64.encodeBase64String(ivByte));

            /**sm4加密字符串*/
            byte[] fileBytes = Files.readAllBytes(guaranteePath);
            log.info("MeiShanUploadBaohanFileChannelService [{}]:保函文件上传-文件大小(明文-字节):{}",acceptOrderNo,fileBytes.length);
            String fileB64Str = Base64.encodeBase64String(fileBytes);
            byte[] encryptFileByte = SM4Util.encrypt_CBC_Padding(keyByte, ivByte, fileB64Str.getBytes(StandardCharsets.UTF_8));

            String encryptFileBase64String = Base64.encodeBase64String(encryptFileByte);
            log.info("MeiShanUploadBaohanFileChannelService [{}]:保函文件上传-文件大小(秘文-字符):{}",acceptOrderNo,encryptFileBase64String.length());
            // 分片
            List<String> fragments = splitByFixedLength(encryptFileBase64String, 1024*200);
            log.info("MeiShanUploadBaohanFileChannelService [{}]:保函文件上传-文件分片数:{}",acceptOrderNo,fragments.size());
            AcceptOrderInfoDO acceptOrderInfoDO = acceptOrderInfoService.getByAcceptOrderNo(acceptOrderNo);
            ProjectInfoDO projectInfoDO = projectInfoService.selectById(acceptOrderInfoDO.getProjectId());
            String filetype = projectInfoDO.getProjectEncryptionFlag().toString();
            // 上传分片
            for (int i = 0; i < fragments.size(); i++) {
                MeiShanEpointUploadBaohanFileReqDTO reqDTO = new MeiShanEpointUploadBaohanFileReqDTO();
                reqDTO.setApplyno(bo.getApplyno());
                reqDTO.setBaohanno(bo.getBaohanno());
                reqDTO.setBaohanfile(fragments.get(i));
                reqDTO.setIndex(String.valueOf(i));
                reqDTO.setFiletype(filetype);
                EpointBaseRespDTO resp = this.doRequest(centerNoEnum, reqDTO);
                if (!EpointRespCodeEnum.isSuccess(resp.getCode())) {
                    return InnerBaseResp.error(resp.getMessage());
                }
            }

            //同步上送完成通知
            BCECPublicKey bcecPublicKey = BCECUtil.convertX509ToECPublicKey(Base64.decodeBase64(sm2PublicKey.getBytes()));
            byte[] keySM2Encrypt = SM2Util.encrypt(SM2Engine.Mode.C1C2C3, bcecPublicKey, keyByte);
            byte[] ivSM2Encrypt = SM2Util.encrypt(SM2Engine.Mode.C1C2C3, bcecPublicKey, ivByte);
            String key = Base64.encodeBase64String(keySM2Encrypt);
            String iv = Base64.encodeBase64String(ivSM2Encrypt);
            log.info("MeiShanUploadBaohanFileChannelService {} << encodeBase64String key: {}", acceptOrderNo, key);
            log.info("MeiShanUploadBaohanFileChannelService {} << encodeBase64String iv: {}", acceptOrderNo, iv);
            String fileMd5 =  DigestUtils.md5Hex(fileBytes);
            innerBaseResp = this.baohanFileCompleteNotice(acceptOrderNo, centerNoEnum, bo, fileMd5,key,iv,filetype);
            acceptOrderInfoService.update(AcceptOrderInfoDO.builder()
                            .uploadKey(key)
                            .uploadIv(iv)
                            .build(),
                    AcceptOrderInfoDOCondition.builder().andAcceptOrderNoEq(acceptOrderNo).build()
            );
        } catch (Exception e) {
            log.error("保函文件上传 >> {}: [{}] 异常:{}", acceptOrderNo, centerNoEnum, e.getMessage(), e);
            innerBaseResp = InnerBaseResp.error("保函文件上传异常:" + e.getMessage());
        }

        return innerBaseResp;
    }

    private InnerBaseResp<String> baohanFileCompleteNotice(String acceptOrderNo, CenterNoEnum centerNoEnum, InnerUploadBaohanFileBo InnerUploadBaohanFileBo, String fileMd5, String key, String iv,String filetype) {

        MeiShanEpointBaohanFileCompleteNoticeReqDTO reqDTO = new MeiShanEpointBaohanFileCompleteNoticeReqDTO();
        reqDTO.setApplyno(InnerUploadBaohanFileBo.getApplyno());
        reqDTO.setBaohanno(InnerUploadBaohanFileBo.getBaohanno());
        reqDTO.setKey(key);
        reqDTO.setIv(iv);
        reqDTO.setBaohanfilemd5(fileMd5);
        reqDTO.setFiletype(filetype);

        return this.meiShanEpointBaohanFileCompleteNoticeChannelService.baohanFileCompleteNotice(acceptOrderNo, centerNoEnum, reqDTO);
    }

    private List<String> splitByFixedLength(String str, int chunkSize) {
        List<String> segments = new ArrayList<>();
        for (int i = 0; i < str.length(); i += chunkSize) {
            int end = Math.min(str.length(), i + chunkSize);
            segments.add(str.substring(i, end));
        }
        return segments;
    }
}
